package com.liziba.unsafe.clone;

import com.liziba.unsafe.UnsafeFactory;
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;

/**
 * <p>
 *      浅克隆工具类
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/5/26 21:08
 */
public class ShallowCloneUtil {


    /**
     * 获取对象的内存地址
     *
     * @Description
     * Unsafe类没有提供直接获取实例对象内存地址的方法，但是可以通过以下方式间接获取。
     * 构建对象A，A包含了我们需要获取内存地址的B对象的引用，这样只有获取到A对象持有的B对象的引用地址，就可以知道B对象的地址了。
     * 我们可以通过Unsafe类获取内存地址的方法public native long getLong(Object var1, long var2)来获取；
     * 此处我们为了方便，通过数组Object[] 添加Object元素，持有Object的引用
     *
     * @return
     */
    public static Long getAddress(Object obj) {

        Object[] objects = new Object[] {obj};
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        int arrayBaseOffset = unsafe.arrayBaseOffset(Object[].class);
        return unsafe.getLong(objects, arrayBaseOffset);
    }


    /**
     * 获取对象的大小
     *
     * @Dscription
     * Java中实例化一个对象时，JVM会在堆中分配非static的Field的内存，其他的static属性或者method在类加载期间或者JVM启动时已经存放在内存中。
     * 所以我们计算对象的大小的时候只需要求和Field的大小就行了，JVM分配内存时，单个实例对象中的Field内存是连续不断地，
     * 因此我们只需获取最大偏移量Filed的偏移量 + 最大偏移量Filed本身的大小即可
     *
     * Java中基本数据类型所占的字节数
     * byte/boolean     1 字节
     * char/short       2 字节
     * int/float        4 字节
     * long/double      8 字节
     * boolean 理论上占1/8字节，实际上按照1byte处理。
     * Java采用的是Unicode编码，每一个字节占8位，一个字节由8个二进制位组成。
     *
     * @param clazz
     * @return
     */
    public static Long size(Class clazz) {

        // 最后一个Filed的内存偏移量
        long maxOffset = 0;
        Class lastFiledClass = null;
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        do {
            for (Field field : clazz.getDeclaredFields()) {
                if (!Modifier.isStatic(field.getModifiers())) {
                    long tmpOffset = unsafe.objectFieldOffset(field);
                    if (tmpOffset > maxOffset) {
                        maxOffset = tmpOffset;
                        lastFiledClass = field.getType();
                    }
                }
            }
        } while ((clazz = clazz.getSuperclass()) != null);
        // 最后一个Field本身的大小
        int lastFiledSize = (boolean.class.equals(lastFiledClass) || byte.class.equals(lastFiledClass)) ? 1 :
                                (short.class.equals(lastFiledClass) || char.class.equals(lastFiledClass)) ? 2 :
                                     (int.class.equals(lastFiledClass) || float.class.equals(lastFiledClass)) ? 4 :  8 ;

        return maxOffset + lastFiledSize;
    }


    /**
     * 申请一块固定大小的内存空间
     *
     * @Description
     * 通过Unsafe的public native long allocateMemory(long var1);申请一块内存空间。
     *
     * @param bytes  需要申请的内存大小
     * @return
     */
    public static Long allocateMemory(long bytes) {
        return UnsafeFactory.getUnsafe().allocateMemory(bytes);
    }


    /**
     * 从原对象内存地址srcAddr复制大小位size的内存到destAddr地址处
     *
     * @param srcAddr           源地址
     * @param destAddr          目标地址
     * @param size              复制内存大小
     */
    public static void copyMemory(long srcAddr, long destAddr, long size) {
        UnsafeFactory.getUnsafe().copyMemory(srcAddr, destAddr, size);
    }


    /**
     * Unsafe未提供直接读取内存转为Java对象的方法，但是可以通过新建一个包含T类型属性的对象将申请的内存地址赋值给T属性
     *
     * @param addr
     * @param <T>
     * @return
     */
    public static <T> T addressConvertObject(long addr) {

        Object[] objects = new Object[] {null};
        Unsafe unsafe = UnsafeFactory.getUnsafe();
        int baseOffset = unsafe.arrayBaseOffset(Object.class);
        unsafe.putLong(objects, baseOffset, addr);
        return (T)objects[0];
    }


    /**
     * 实现对象的浅克隆
     *
     * @Description
     * 数组的无法通过此方法实现克隆，数组类是在JVM运行时动态生成的
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> T shallowClone(T t) {
        Class<?> clazz = t.getClass();
        if (clazz.isArray()) {
            Object[] objects = (Object[]) t;
            return (T) Arrays.copyOf(objects, objects.length);
        }
        Long srcAddr = getAddress(t);
        Long size = size(clazz);
        Long destAddr = allocateMemory(size);
        copyMemory(srcAddr, destAddr, size);
        return addressConvertObject(destAddr);
    }


}
